package com.wpi.iCoach;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Random;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

/** 
 * This activity class displays a splash screen and performs some initialization of the 
 * application database prior to the application beginning. Specifically, a connection is 
 * made to a web-hosted xml file that contains location information. This information is
 * downloaded and added to the application database. Further work is needed to ensure
 * that the data being extracted does not already exist in the database. 
 * 
 * @author Kenneth J. Loomis (kjloomis)
 * @version June 2012
 */
public class Downloading extends Activity {
	
	/** 
	 * Programmatic connection to the progress bar defined in the graphic layout.
	 * @see res/layout/downloading.xml
	 */  
	protected static ProgressBar pbProg;
	/** 
	 * Programmatic connection to the text view defined in the graphic layout.
	 * @see res/layout/downloading.xml
	 */ 
	protected static TextView tvMsg;
	/** 
	 * Instance of the Location Database Manager which allows access to the static
	 * database shared by all activities in the application.
	 * @see LocDBMgr.java
	 */ 
	protected LocDBMgr dbm;
	
	/**
	 * Instantiates the activity. Binds the graphical elements to the programmatic
	 * representations (i.e. Binds progress bar and text view).
	 * @param savedInstanceState
	 */
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView ( R.layout.downloading );
		
		dbm = new LocDBMgr ( this );
		
		pbProg = ( ProgressBar ) findViewById ( R.id.pbProgress );
		tvMsg = ( TextView ) findViewById ( R.id.tvDown );
		
		try{
			new GetDataFromUrl().execute( this );
		}
		catch ( Exception e )
		{
			e.printStackTrace( );
			Toast t3 = Toast.makeText( this, "Unable to create database from external source.", Toast.LENGTH_SHORT );
			t3.show();
		}
		
	}

	/**
	 * Automatically called when the activity pauses. Finish is called which terminates
	 * the activity. 
	 * @param null
	 */
	@Override
	protected void onPause ( ) {
		// TODO Auto-generated method stub
		super.onPause ( );

		finish ( );
	}
	
	/** 
	 * This class performs the task of connecting to a web-hosted xml file which
	 * contains location information in a separate thread from the main activity
	 * thread. The data for each location is extracted and added to a Context Value 
	 * package to be added to the application database. As a side effect the progress
	 * bar of the parent activity class is updated based on the progress of this thread. 
	 * 
	 * @author Kenneth J. Loomis (kjloomis)
	 * @version June 2012
	 */
	protected class GetDataFromUrl extends AsyncTask <Context, Integer, ContentValues[]>
	{
		/** Integer representation of the progress of the thread (0 to 100) */
		private int progress = 0;
		
		/** 
		 * Holds the parent context so that the pop-up message will be displayed
		 * on the correct activity.
		 */
		private Context parentCntxt;
		
		/** Array of progress messages */
		private String progStr [] = {"Checking Data Version...",
				"Connecting to External Database...",
				"Extracting Data...",
				"Inserting Data...",
				"Finalizing..."	};
		
		/** Definitions of the boundaries for when a progress message should be updated. */
		private int progBoundary [] = { 0, 5, 15, 90, 100 };
		
		/** 
		 * Connects to a web-hosted xml file, downloads the file, and returns an input
		 * stream to that file.
		 * 
		 * @return InputStream
		 * @throws IOException
		 */
		private InputStream GetHTTPConnection ( ) throws IOException
		{
			InputStream in = null;
			int gotResponse = -1;
			
			/** Hardcoded reference to the address of the xml file */
			URL url = new URL ( "http://users.wpi.edu/~kjloomis/iCoach/locations.xml" );
			URLConnection conn = url.openConnection ( );
			
			/** Ensure that the connection type that was made was an http connection */
			if ( ! ( conn instanceof HttpURLConnection ) )
			{
				//Toast t1 = Toast.makeText( cntxt, "Invalid HTTP connection.", Toast.LENGTH_SHORT );
				//t1.show();
				throw new IOException ( "Invalid HTTP connection." );
			}
			
			try 
			{
				HttpURLConnection httpConn = ( HttpURLConnection ) conn;
				httpConn.setAllowUserInteraction( false );
				httpConn.setInstanceFollowRedirects( true );
				httpConn.setRequestMethod( "GET" );
				httpConn.connect ( );
				gotResponse = httpConn.getResponseCode ( );
				if ( gotResponse == HttpURLConnection.HTTP_OK )
					in = httpConn.getInputStream ( );
			}
			catch ( Exception e )
			{
				//Toast t2 = Toast.makeText( cntxt, "Could not connect to HTTP server.", Toast.LENGTH_SHORT );
				//t2.show();
				throw new IOException ( "Could not connect to HTTP server. " );
			}
			
			progress += 20;
			publishProgress( progress );
			
			return in;
				
		}
		
		/** 
		 * This the the main function of the thread which will be running in the background. Parses the 
		 * downloaded xml file and extracts the location data. The format of the xml file is as follows:
		 * <xsd:element name='Loc_Name' type='string'/>
		 * <xsd:element name='Latitude' type='float'/>
		 * <xsd:element name='Longitude' type='float'/>
		 * <xsd:element name='Locations'>
		 *	<xsd:element name='Location'>
		 *		  <xsd:complexType>
		 *			<xsd:all>
		 *			  <xsd:element ref='Loc_Name'/>
		 *			  <xsd:element ref='Latitide'/>
		 *			  <xsd:element ref='Longitude'/>
		 *			  <xsd:element ref='Loc_Type'/>
		 *			</xsd:all>
		 *		  </xsd:complexType>
		 *		</xsd:element>
		 *	</xsd:element> 
		 * Each location is added to the array of context values.  
		 * @param ctx
		 * @return ContentValues[]
		 */
		@Override
		protected ContentValues[] doInBackground(Context... ctx) {
			// TODO Auto-generated method stub

			progress = progBoundary[1];
			publishProgress( progress );
			
			if ( LocDBMgr.DB_DATA_VERSION > 0 )
				return null;
			
			InputStream in = null;
			ContentValues cvs[] = null;
			parentCntxt = ctx[0];
			
			try {
				in = GetHTTPConnection ( );
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			
			progress = progBoundary[2];
			publishProgress( progress );
			
			if ( in != null )
			{
				Document doc;
				DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
				DocumentBuilder dber;
				
				try 
				{
					dber = dbf.newDocumentBuilder();
					doc = dber.parse( in );
					doc.getDocumentElement().normalize();
					
					NodeList itemNodes = doc.getElementsByTagName ("Location");
					
					cvs = new ContentValues [ itemNodes.getLength() ];
					float fltProg = progress;
					float progInc = ( float ) (progBoundary[3]-progBoundary[2]) / ( float ) itemNodes.getLength() ;
					for ( int iter = 0; iter < itemNodes.getLength(); iter++ )
					{
						Node oneNode = itemNodes.item( iter );
						if ( oneNode.getNodeType() == Node.ELEMENT_NODE )
						{
							Element oneElement = ( Element ) oneNode;
							cvs [ iter ] = new ContentValues ( );
							// Loc Name
							NodeList locList = oneElement.getElementsByTagName("Loc_Name");
		                    Element locElement = (Element) locList.item(0);
		                    NodeList LocNameList = locElement.getChildNodes();
		                    cvs[ iter ].put ( LocDBMgr.KEY_LOC_NAME, 
		                    				  ( ( Node ) LocNameList.item ( 0 ) ).getNodeValue( ).trim( ) );
		                    	                    
		                    // Loc Lat
							NodeList latList = oneElement.getElementsByTagName("Latitude");
		                    Element latElement = (Element) latList.item(0);
		                    NodeList latValList = latElement.getChildNodes();
		                    cvs[ iter ].put ( LocDBMgr.KEY_LAT, ((Node)latValList.item(0)).getNodeValue().trim() );
		                    	
		                    // Loc Long
							NodeList lonList = oneElement.getElementsByTagName("Longitude");
		                    Element lonElement = (Element) lonList.item(0);
		                    NodeList lonValList = lonElement.getChildNodes();
		                    cvs[ iter ].put ( LocDBMgr.KEY_LON, ((Node)lonValList.item(0)).getNodeValue().trim() );
		                    
		                    // Loc Type
							NodeList typeList = oneElement.getElementsByTagName("Loc_Type");
		                    Element typeElement = (Element) typeList.item(0);
		                    NodeList typeValList = typeElement.getChildNodes();
		                    for ( int iter2 = 0; iter2 < LocDBMgr.LOC_TYPES.length; iter2++ )
		                    {
		                    	if ( LocDBMgr.LOC_TYPES[ iter2 ].compareTo( ((Node)typeValList.item(0)).getNodeValue().trim() ) == 0 )
		                    	cvs[ iter ].put ( LocDBMgr.KEY_LOC_TYPE, iter2 );
		                    }	
		                    
		                    Random rand = new Random();
		                    
		                    // Region
		                    //cvs[ iter ].put ( LocDBMgr.KEY_REGION, 1 );
		                    cvs[ iter ].put ( LocDBMgr.KEY_VISITS, rand.nextInt(10) );
		                    cvs[ iter ].put ( LocDBMgr.KEY_RANK, rand.nextInt(100)/( float )20 );
		
		                    // Update progress meter
		                    fltProg += progInc;
		                    progress = ( int ) fltProg;
		                    publishProgress( progress );
						}
						
					}
							
				} 
				catch (ParserConfigurationException e) 
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				catch ( Exception e )
				{
					e.printStackTrace();
				}
			}
			
			return cvs;

		}
		
		/**
		 * Initialization of the background thread. Prepares the progress bar by initializing
		 * it to 0. (Called once before doInBackground).
		 */
		@Override
		protected void onPreExecute() {
			// TODO Auto-generated method stub
			
			progress = progBoundary[0];
			publishProgress(progress);
			pbProg.setMax(100);
			super.onPreExecute();

		}

		/**
		 * Periodically called to update the progress bar. All values passed to publishProgress will
		 * eventually be processed by this function. Multiple progress update values may be handled 
		 * by one call to this function, however, only the first will be processed.
		 * (Alternate version proposed that would handle the last [most current] value.)
		 * @param values (one or more integer value)
		 */
		@Override
		protected void onProgressUpdate(Integer... values) {
			super.onProgressUpdate(values);
			
			if ( values[0] < progBoundary[1])
				tvMsg.setText( progStr[0] );
			else if ( values[0] < progBoundary[2])
				tvMsg.setText( progStr[1] );
			else if ( values[0] < progBoundary[3])
				tvMsg.setText( progStr[2] );
			else if ( values[0] < progBoundary[4])
				tvMsg.setText( progStr[3] );
			else if ( values[0] >= progBoundary[4])
				tvMsg.setText( progStr[4] );
			pbProg.setProgress(values[0]);
		}

		/**
		 * Finalize the task to be done by the thread. (Automatically called once after completion of doInBackground.)
		 * Adds all the locations extracted from the xml file to the database in one batch insert and
		 * produces a pop-up message that says how many locations were added. Produces a sleeping thread that will 
		 * load that next activity after some short wait period.
		 * @param cvs (ContextValues array)
		 */
		@Override
		protected void onPostExecute ( ContentValues [] cvs ) {
			
			progress = progBoundary[3];
			publishProgress( progress );
			
			if ( cvs != null )
			{
				dbm.Open();
				Toast toast = Toast.makeText( parentCntxt, "Added " + dbm.BatchAddLocations( cvs ) + " locations.", Toast.LENGTH_SHORT );
				toast.show();
				dbm.Close();
			}
			
			progress = progBoundary[4];
			publishProgress( progress );
			
			Thread timer = new Thread ( ) 
			{
				public void run ( ) 
				{
					try 
					{
						sleep ( 750 );
					} 
					catch ( InterruptedException e )
					{
						e.printStackTrace ( );
					}
					finally 
					{
						Intent openApp = new Intent ( "com.wpi.iCoach.MAINMENU" );
						startActivity ( openApp );
					}
					
				}
			};
			timer.start ( );

		}
		
	}

	
}
